/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */
#include "bmsn-path-selector-tag.h"
#include "ns3/random-variable.h"
#include <list>
#include <stdlib.h>

NS_LOG_COMPONENT_DEFINE ("BmsnPathSelectorTag");

namespace ns3 {
namespace eecityu {

using namespace std;

NS_OBJECT_ENSURE_REGISTERED (BmsnPathSelectorTag);

BmsnPathSelectorTag::BmsnPathSelectorTag(void)
  : uniformRandom ()
{
  NS_LOG_FUNCTION_NOARGS ();

  // initialize the tag to some random value
  SetTagValue (GenerateRandom ());
}

BmsnPathSelectorTag::BmsnPathSelectorTag(uint64_t v)
{
  NS_LOG_FUNCTION(v);

  // initialize the tag to some random value
  SetTagValue (v);
}

TypeId 
BmsnPathSelectorTag::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::eecityu::BmsnPathSelectorTag")
    .SetParent<Tag> ()
    ;
  return tid;
}

TypeId
BmsnPathSelectorTag::GetInstanceTypeId (void) const
{
  return GetTypeId ();
}

uint32_t 
BmsnPathSelectorTag::GetSerializedSize (void) const
{
  // m_value is 64 bits, or 8 bytes
  return 8;
}

void 
BmsnPathSelectorTag::Serialize (TagBuffer i) const
{
  i.WriteU64 (m_value);
}

void 
BmsnPathSelectorTag::Deserialize (TagBuffer i)
{
  m_value = i.ReadU64 ();
}

/**
 * \todo pring the tag value in binary format
 */
void 
BmsnPathSelectorTag::Print (std::ostream &os) const
{
  os << "BmsnPathSelectorTag=" << m_value;
}

void 
BmsnPathSelectorTag::SetTagValue (uint64_t v)
{
  NS_LOG_FUNCTION (v);

  m_value = v;
}

uint64_t 
BmsnPathSelectorTag::GetTagValue ()
{
  return m_value;
}

uint8_t 
BmsnPathSelectorTag::ShiftOutTwoLowestBits ()
{
  NS_LOG_FUNCTION_NOARGS ();
  if (m_value == 0)
    NS_LOG_WARN ("The tag value is zero and no randomness is introduced "
    << "when choosing from multiple exits.");
  // the value to be return. Load it with the two bits from the left-side of the tag value
  uint8_t v = m_value & 0x3;
  // consume the two bits from the tag value
  m_value = m_value >> 2;
  return v;
}

BmsnPathSelectorTag::ExitDirection 
BmsnPathSelectorTag::SelectExitDirection (bool goNorth, bool goEast, bool goSouth, bool goWest)
{
  NS_LOG_FUNCTION (goNorth << goEast << goSouth << goWest);

  // choose two bits from the tag value. Thess bits value, either 00, 01, 10, or 11
  // indicate which one of the four feasible exits are chosen  
  uint32_t randSelector = m_value & 0x3;
  NS_LOG_LOGIC ("Random selector value = " << randSelector);
  NS_LOG_LOGIC ("Tag value before discarding bits = " << m_value);
  // store the exit direction to be returned to caller
  ExitDirection returnDirection;

  if (m_value == 0)
  {
    NS_LOG_WARN ("The tag value is zero already. BmsnRouting no longer"
      << " reads BmsnPathSelectorTag when choosing exit interfaces."
      );
    returnDirection = None;
  } // if block of "if (m_value == 0)"
  else
  {
    // discard the two bits
    m_value = m_value >> 2;
    NS_LOG_LOGIC ("Tag value after discarding bits = " << m_value);
    // print out a warning message if the tag value goes to zero
    if (m_value == 0)
      NS_LOG_WARN ("The tag value reaches zero. BmsnRouting no longer"
        << " reads BmsnPathSelectorTag when choosing exit interfaces."
        );

    // find out the number of feasible exits.
    uint32_t numFeasible = goNorth + goEast + goSouth + goWest;
    NS_ASSERT_MSG (
      numFeasible == 1 || 
      numFeasible == 2 || 
      numFeasible == 4, 
      "Impossible to have number of fesible exits = " << numFeasible
      );
    // prepare a list of feasible exits from which one is shosen according to tag value
    list<ExitDirection> feasibleExit;
    if (goNorth) feasibleExit.push_back (North);
    if (goEast)  feasibleExit.push_back (East) ;
    if (goSouth) feasibleExit.push_back (South);
    if (goWest)  feasibleExit.push_back (West) ;
    NS_ASSERT_MSG (!feasibleExit.empty (), "The list of fessible exits cannot be empty.");
    // walk through all feasible exit. Each ealk decrements the randSelector by 1,
    // and the walk stops until the randSelector rearches 0.
    list<ExitDirection>::const_iterator chosenIter = feasibleExit.begin ();
    while (randSelector-- != 0)
    {
      // wrap around the chosenIter if it reaches the end of the list of feasible exits
      if (++chosenIter == feasibleExit.end ())
        chosenIter = feasibleExit.begin ();
    }
    returnDirection = *chosenIter;
  } // else block of "if (m_value == 0)"

  NS_LOG_LOGIC ("Exit direction chosen = " << returnDirection);
  return returnDirection;
}

uint64_t 
BmsnPathSelectorTag::GenerateRandom ()
{
  NS_LOG_FUNCTION_NOARGS ();
  
  // the min and max for the range to generate a uniform random integer
  const uint32_t MIN = 0;
  const uint32_t MAX = 0xfffffffe;
  // store a 32-bit random number used to form a 64-bit random number
  uint32_t randValue;
  // generate 32 random bits to the highest 32-bit position
  randValue = uniformRandom.GetInteger (MIN, MAX);
  NS_LOG_LOGIC ("Generate the first 32-bit random numbers = " << randValue);
  uint64_t r = randValue;
  // set the most significant bit of the tag value
  r |= 0x80000000;
  // position these 32 random bits to the highest significant part
  r = r << 32;
  // generate 32 random bits for the lowest 32-bit position
  randValue = uniformRandom.GetInteger (MIN, MAX);
  r |= randValue;
  NS_LOG_LOGIC ("Generate the second 32-bit random numbers = " << randValue);
  // return the random number generated
  NS_LOG_LOGIC ("Generated a random integer " << r);
  return r;
}

ostream& 
operator<< (ostream& os, const BmsnPathSelectorTag& tag)
{
  os << tag.m_value;
  return os;
}

ostream& 
operator<< (ostream& os, const BmsnPathSelectorTag::ExitDirection& e)
{
  switch (e)
  {
  case BmsnPathSelectorTag::East:  os << "East";  break;
  case BmsnPathSelectorTag::South: os << "South"; break;
  case BmsnPathSelectorTag::West:  os << "West";  break;
  case BmsnPathSelectorTag::North: os << "North"; break;
  case BmsnPathSelectorTag::None:  os << "None";  break;
  default:  NS_ASSERT_MSG (0, "No such ExitDirection!");
  };

  return os;
}

} // namespace eecityu
} // namespace ns3
